#include <stdio.h>
#include <math.h>
#include <string.h>
#include "slave.h"

__thread_local volatile unsigned long get_reply[2],put_reply[2];
__thread_local double ex_slave[2][TZ1][TZ2];
__thread_local double ey_slave[2][TZ1][TZ2];
__thread_local double hz_slave[2][TZ1][TZ2];
__thread_local double shade_hz_left[2][TZ1];//hz[i][j-1]
__thread_local double shade_hz_up[2][TZ2];//hz[i-1][j]
__thread_local double shade_ex[2][TZ1];//ex[i][j+1]
__thread_local double shade_ey[2][TZ2];//ey[i+1][j]
__thread_local int my_task_id,my_next_task_id,my_id,my_task_num,my_num_x,my_num_y,thread_nums,my_iter;
__thread_local int dma_get_num[2]={0},dma_put_num[2]={0};


extern double ex[NX][NY+1];
extern double ey[NX+1][NY];
extern double hz[NX][NY];
//extern int task_num[64];
extern int num_x,num_y,thread_num,iter;

#define min(a,b) ((a>b)?(b):(a))
#define max(a,b) ((a>b)?(a):(b))

void while_tmp(volatile unsigned long *A, int B)
{
    while( (*A)!=B );
}

#ifdef PROFILING 
//__thread_local_fix volatile unsigned long total_start,total_end;
__thread_local_fix volatile unsigned long dma_get_start,dma_put_start,dma_get_end,dma_put_end;
__thread_local_fix volatile unsigned long comp_start,comp_end;

extern unsigned long slave_comp_count[64];
extern unsigned long slave_dma_count[64];
//extern unsigned long slave_count[64];
unsigned long rtc_()
{
  unsigned long rpcc;
  asm volatile("rcsr  %0,4":"=r"(rpcc));
  return rpcc;
}

#endif


void func()
{
    int bsize,stride,tsize;
//    volatile unsigned int get_reply[2],put_reply[2];
    int i,j,k,ii,jj,next_i,next_j;
    int index,last,next;
    
    my_id=athread_get_id(-1);
    my_num_y=num_y;
    my_num_x=num_x;
    //my_iter=iter;
    thread_nums=thread_num;
    
    if(my_id>=thread_nums)
        my_task_num=0;
    else if(my_id >= ((my_num_x * my_num_y)%thread_nums) )
        my_task_num = (my_num_x * my_num_y) /thread_nums;
    else 
        my_task_num = (my_num_x * my_num_y) /thread_nums + 1;      
    // my_task_num=task_num[my_id];
    // printf("%d",my_task_num);

    for(my_iter=0; my_iter<TMAX; my_iter++)
    {
	
     //loop1
    for (k=0; k<my_task_num; k++)//
    {
        index=k%2;
        next=(k+1)%2; 
        last=next;

        my_task_id=my_id+k*(thread_nums);
        i = my_task_id / my_num_y * TZ1;
        if(i>0)
            break;
        j = my_task_id % my_num_y * TZ2;
        
        //compute
#ifdef PROFILING   
        //total_start=rtc_();
        comp_start=rtc_();        
#endif        
        for(jj=0;jj<min(NY,TZ2);jj++)
            ey_slave[0][0][jj]=my_iter;
#ifdef PROFILING      
        comp_end=rtc_();
        dma_put_start=rtc_();
#endif
        //write back
        //ey_slave[0,0:TZ2-1]->ey[0,j:j+TZ2-1]
        put_reply[index]=0;
        dma_put_num[index]=0;
        athread_put(PE_MODE
            , &ey_slave[index][0][0], &ey[0][j]
            , TZ2*8, (void*)&put_reply[index]
            , 0, 0); 
        dma_put_num[index]++;
        if(k>0)
            while(put_reply[last]!=dma_put_num[last]);
        
#ifdef PROFILING    
        dma_put_end=rtc_();
        //total_end=rtc_();
        slave_comp_count[my_id]=slave_comp_count[my_id]+(comp_end-comp_start);
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start);
        comp_end=comp_start=dma_put_end=dma_put_start=0;
        //slave_count[my_id]=slave_count[my_id]+(total_end-total_start);
#endif   
#ifdef PROFILING 
    dma_put_start=rtc_();
#endif
    while(put_reply[index]!=dma_put_num[index]);
#ifdef PROFILING
    dma_put_end=rtc_();
    slave_dma_count[my_id]+=(dma_put_end-dma_put_start);
    dma_put_start=dma_put_end=0;
#endif
    }//end of K
    athread_syn(ARRAY_SCOPE,-1);
    
    //loop2
    my_task_id=my_id;
    i = my_task_id / my_num_y * TZ1;
    j = my_task_id % my_num_y * TZ2;
    
    //step 0: pipeline start; startup read in for the first tile
    index=0;
    get_reply[index]=0;
    dma_get_num[index]=0;
    //ey[i:i+TZ1-1,j:j+TZ2-1]->ey_slave[index][0:TZ1-1,j:TZ2-1]
    bsize=TZ2*8;
    stride=(NY-TZ2)*8;
    tsize=bsize*TZ1;
    athread_get(PE_MODE
        , &ey[i][j], &ey_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    dma_get_num[index]++;
    //hz[i:i+TZ1-1,j:j+TZ2-1]->hz_slave[index][0:TZ1-1,j:TZ2-1]
    bsize=TZ2*8;
    stride=(NY-TZ2)*8;
    tsize= bsize*TZ1;
    athread_get(PE_MODE
        , &hz[i][j], &hz_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    dma_get_num[index]++;
    
    if(max(i,1)!=1)
    {   
        athread_get(PE_MODE
            ,&hz[i-1][j],&shade_hz_up[index][0]
            ,TZ2*8,(void*)&get_reply[index]
            ,0,0,0); 
        dma_get_num[index]++;
    }   
    //main part
    for (k=0; k<my_task_num; k++)
    {
        index=k%2;
        next=(k+1)%2; 
        last=next;

        my_task_id=my_id+k*(thread_nums);
        i = my_task_id / my_num_y * TZ1;
        j = my_task_id % my_num_y * TZ2;
        //printf("i=%d,j=%d\n",i,j);
        //step1: start up the next readin
        if (k<my_task_num-1)
        {
            my_next_task_id=my_task_id+thread_nums;
            next_i = my_next_task_id / my_num_y * TZ1;
            next_j = my_next_task_id % my_num_y * TZ2;
            //printf("next_i=%d,next_j=%d\n",next_i,next_j);
#ifdef PROFILING    
        //total_start=rtc_();
        dma_get_start=rtc_();
#endif            
            get_reply[next]=0;
            dma_get_num[next]=0;
            //ey[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->ey_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY-TZ2)*8;
            tsize=bsize*TZ1;
            athread_get(PE_MODE
                , &ey[next_i][next_j], &ey_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            //printf("ey[%d][%d]=%lf,ey_slave[%d][0][0]=%lf\n",next_i,next_j,ey[next_i][next_j],next,ey_slave[next][0][0]);
            //hz[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->hz_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY-TZ2)*8;  
            tsize= bsize*TZ1;
            athread_get(PE_MODE
                , &hz[next_i][next_j], &hz_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            dma_get_num[next]+=2;
            if(max(next_i,1)!=1)
            {
                athread_get(PE_MODE
                    ,&hz[next_i-1][next_j],&shade_hz_up[next][0]
                    ,TZ2*8,(void*)&get_reply[next]
                    ,0,0,0); 
                dma_get_num[next]++;
            }
        }      
        //step 2: wait for the readin of the current tile
        while(get_reply[index]!=dma_get_num[index]);
       
#ifdef PROFILING  
        dma_get_end=rtc_();
        comp_start=rtc_();
#endif 
        //step 3: compute this tile
        //inner region
        for(ii=1; ii<TZ1; ii++)
        {
            for(jj=0; jj<TZ2; jj++)
            {
                ey_slave[index][ii][jj] = ey_slave[index][ii][jj] - 0.5*(hz_slave[index][ii][jj] - hz_slave[index][ii-1][jj]);
            } 
        }
        //border region
        if(max(i,1)!=1)
        {
            for(jj=0;jj<TZ2;jj++)
            {
                ey_slave[index][0][jj] = ey_slave[index][0][jj] - 0.5*(hz_slave[index][0][jj] - shade_hz_up[index][jj]);
            }
        }
        
#ifdef PROFILING      
        comp_end=rtc_();
        dma_put_start=rtc_();
#endif    
        //step 4: start up current write back
        put_reply[index]=0;
        dma_put_num[index]=0;
        //ey_slave[0:TZ1-1,0:TZ2-1]->ey[i:i+TZ1-1,j:j+TZ2-1]
        bsize=TZ2*8;
        stride=(NY-TZ2)*8; 
        tsize= bsize*(TZ1);
        athread_put(PE_MODE 
            , &ey_slave[index][0][0], &ey[i][j]
            , tsize, (void*)&put_reply[index]
            , stride, bsize);
        dma_put_num[index]++;
        
        if(k>0)
        {
            while(put_reply[last]!=dma_put_num[last]);
            //while_tmp(put_reply[last],dma_put_num[last]);
        }
#ifdef PROFILING 
        dma_put_end=rtc_();
        //total_end=rtc_();
        slave_comp_count[my_id]=slave_comp_count[my_id]+(comp_end-comp_start);
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start)+(dma_get_end-dma_get_start);
        //slave_count[my_id]=slave_count[my_id]+(total_end-total_start);
#endif       
    }//end of k
#ifdef PROFILING      
        dma_put_start=rtc_();
#endif
    //step 5: wait for previous write back
    //while_tmp(put_reply[index],dma_put_num[index]);
    while(put_reply[index]!=dma_put_num[index]);
#ifdef PROFILING 
        dma_put_end=rtc_();
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start);
#endif        
    athread_syn(ARRAY_SCOPE,-1);
    //loop3
    my_task_id=my_id;
    i = my_task_id / my_num_y * TZ1;
    j = my_task_id % my_num_y * TZ2;
    //step 0: pipeline start; startup read in for the first tile
    index=0;
    get_reply[index]=0;
    dma_get_num[index]=0;
    
    bsize=TZ2*8;
    stride=(NY+1-TZ2)*8;
    tsize=bsize*TZ1;
    athread_get(PE_MODE
        , &ex[i][j], &ex_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    //hz[i:i+TZ1-1,j:j+TZ2-1]->hz_slave[0:TZ1-1,0:TZ2-1]
    bsize=TZ2*8;
    stride=(NY-TZ2)*8;   
    tsize= bsize*TZ1;
    athread_get(PE_MODE
        , &hz[i][j], &hz_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    dma_get_num[index]+=2;
    if(max(j,1)!=1)
    {
        bsize=8;
        stride=(NY-1)*8;
        tsize=bsize*TZ1;
        athread_get(PE_MODE
            ,&hz[i][j-1],&shade_hz_left[index][0]
            ,tsize,(void*)&get_reply[index]
            ,0,stride,bsize); 
        dma_get_num[index]++;
    }
    
    
    for (k=0; k<my_task_num; k++)
    {
        index=k%2;
        next=(k+1)%2; 
        last=next;

        my_task_id=my_id+k*(thread_nums);
        i = my_task_id / my_num_y * TZ1;
        j = my_task_id % my_num_y * TZ2;
        //step1: start up the next readin
        if(k<my_task_num-1)
        {
            my_next_task_id=my_task_id+thread_nums;
            next_i = my_next_task_id / my_num_y * TZ1;
            next_j = my_next_task_id % my_num_y * TZ2;
#ifdef PROFILING    
        //total_start=rtc_();
        dma_get_start=rtc_();
#endif             
            get_reply[next]=0;
            dma_get_num[next]=0;
            //ex[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->ex_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY+1-TZ2)*8;
            tsize=bsize*TZ1;
            athread_get(PE_MODE
                , &ex[next_i][next_j], &ex_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            //hz[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->hz_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY-TZ2)*8;   
            tsize= bsize*TZ1;
            athread_get(PE_MODE
                , &hz[next_i][next_j], &hz_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            dma_get_num[next]+=2;
            if(max(next_j,1)!=1)
            {
                bsize=8;
                stride=(NY-1)*8;
                tsize=bsize*TZ1;
                athread_get(PE_MODE
                    ,&hz[next_i][next_j-1],&shade_hz_left[next][0]
                    ,tsize,(void*)&get_reply[next]
                    ,0,stride,bsize); 
                dma_get_num[next]++;
            }
        }
        //step 2: wait for the readin of the current tile
        while(get_reply[index]!=dma_get_num[index]);
        //compute
#ifdef PROFILING  
        dma_get_end=rtc_();
        comp_start=rtc_();
#endif    
        //step 3: compute this tile
        //inner region
        for(ii=0; ii<TZ1; ii++)
        {
            for(jj=1; jj<TZ2; jj++)
            {
                ex_slave[index][ii][jj] = ex_slave[index][ii][jj] - 0.5*(hz_slave[index][ii][jj] - hz_slave[index][ii][jj-1]);
            } 
        }
        //border region
        if(max(j,1)!=1)
        {
            for(ii=0;ii<TZ2;ii++)
                ex_slave[index][ii][0] = ex_slave[index][ii][0] - 0.5*(hz_slave[index][ii][0] - shade_hz_left[index][ii]);
        }

#ifdef PROFILING      
        comp_end=rtc_();
        dma_put_start=rtc_();
#endif         
        //step 4: start up current write back
        //ey_slave[0:TZ1-1,0:TZ2-1]->ey[i:i+TZ1-1,j:j+TZ2-1]
        put_reply[index]=0;
        dma_put_num[index]=0;
        bsize=TZ2*8;
        stride=(NY+1-TZ2)*8; 
        tsize= bsize*TZ1;
        athread_put(PE_MODE 
            , &ex_slave[index][0][0], &ex[i][j]
            , tsize, (void*)&put_reply[index]
            , stride, bsize);
        dma_put_num[index]++;
        if(k>0)
        {
            //while_tmp(put_reply[last],dma_put_num[last]);
            while(put_reply[last]!=dma_put_num[last]);
        }
        
#ifdef PROFILING 
        dma_put_end=rtc_();
        //total_end=rtc_();
        slave_comp_count[my_id]=slave_comp_count[my_id]+(comp_end-comp_start);
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start)+(dma_get_end-dma_get_start);
        //slave_count[my_id]=slave_count[my_id]+(total_end-total_start);
#endif
    }//end of k
#ifdef PROFILING      
        dma_put_start=rtc_();
#endif
    //step 5: wait for previous write back
    //while_tmp(put_reply[index],dma_put_num[index]);
    while(put_reply[index]!=dma_put_num[index]);
#ifdef PROFILING 
        dma_put_end=rtc_();
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start);
#endif 
    athread_syn(ARRAY_SCOPE,-1);
    
     //loop4   
    my_task_id=my_id;
    i = my_task_id / my_num_y * TZ1;
    j = my_task_id % my_num_y * TZ2;
    index=0;
    
    get_reply[index]=0;
    dma_get_num[index]=0;
    //ex[i:i+TZ1-1,j:j+TZ2-1]->ex_slave[0:TZ1-1,j:TZ2-1]
    bsize=TZ2*8;
    stride=(NY+1-TZ2)*8;
    tsize=bsize*TZ1;
    athread_get(PE_MODE
        , &ex[i][j], &ex_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    //ey[i:i+TZ1-1,j:j+TZ2-1]->ey_slave[0:TZ1-1,j:TZ2-1]
    bsize=TZ2*8;
    stride=(NY-TZ2)*8;
    tsize=bsize*TZ1;
    athread_get(PE_MODE
        , &ey[i][j], &ey_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    //hz[i:i+TZ1-1,j:j+TZ2-1]->hz_slave[0:TZ1-1,0:TZ2-1]
    bsize=TZ2*8;
    stride=(NY-TZ2)*8;   
    tsize= bsize*TZ1;
    athread_get(PE_MODE
        , &hz[i][j], &hz_slave[index][0][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);
    //fetch ex shada data
    bsize=8;
    stride=(NY+1-1)*8;  
    tsize= bsize*(TZ1);
    athread_get(PE_MODE
        ,&ex[i][j+TZ2], &shade_ex[index][0]
        , tsize, (void*)&get_reply[index]
        , 0, stride, bsize);   
    //fetch ey shade data
    athread_get(PE_MODE
        ,&ey[i+TZ1][j],&shade_ey[index][0]
        ,TZ2*8,(void*)&get_reply[index]
        ,0,0,0);
    dma_get_num[index]+=5;
    
    
    for (k=0; k<my_task_num; k++)
    {
        index=k%2;
        next=(k+1)%2; 
        last=next;
        my_task_id=my_id+k*(thread_nums);
        i = my_task_id / my_num_y * TZ1;
        j = my_task_id % my_num_y * TZ2;
        //printf("i=:%d,j=%d\n",i,j);
        if (k<my_task_num-1)
        {
            my_next_task_id=my_task_id+thread_nums;
            next_i = my_next_task_id / my_num_y * TZ1;
            next_j = my_next_task_id % my_num_y * TZ2;
#ifdef PROFILING    
        //total_start=rtc_();
        dma_get_start=rtc_();
#endif
            get_reply[next]=0;
            dma_get_num[next]=0;
            //ex[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->ex_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY+1-TZ2)*8;
            tsize=bsize*TZ1;
            athread_get(PE_MODE
                , &ex[next_i][next_j], &ex_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            //ey[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->ey_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY-TZ2)*8;
            tsize=bsize*TZ1;
            athread_get(PE_MODE
                , &ey[next_i][next_j], &ey_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            //hz[next_i:next_i+TZ1-1,next_j:next_j+TZ2-1]->hz_slave[0:TZ1-1,0:TZ2-1]
            bsize=TZ2*8;
            stride=(NY-TZ2)*8;   
            tsize= bsize*TZ1;
            athread_get(PE_MODE
                , &hz[next_i][next_j], &hz_slave[next][0][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
            //dma_get_num+=2;
            //fetch ex shada data
            bsize=8;
            stride=(NY+1-1)*8;  
            tsize= bsize*(TZ1);
            athread_get(PE_MODE
                ,&ex[next_i][next_j+TZ2], &shade_ex[next][0]
                , tsize, (void*)&get_reply[next]
                , 0, stride, bsize);
  
            //fetch ey shade data
            athread_get(PE_MODE
                ,&ey[next_i+TZ1][next_j],&shade_ey[next][0]
                ,TZ2*8,(void*)&get_reply[next]
                ,0,0,0);
            dma_get_num[next]+=5;
        }      
        while (get_reply[index]!=dma_get_num[index]);  
        
        //compute
#ifdef PROFILING  
        dma_get_end=rtc_();
        comp_start=rtc_();
#endif        
        for(ii=0; ii<TZ1-1; ii++)
            for(jj=0; jj<TZ2-1; jj++)
            {
                hz_slave[index][ii][jj] = hz_slave[index][ii][jj]-0.7*(ex_slave[index][ii][jj+1] \
                             - ex_slave[index][ii][jj] + ey_slave[index][ii+1][jj] - ey_slave[index][ii][jj]);
            }
        for(jj = 0; jj < TZ2-1; jj++)
        { 
            hz_slave[index][TZ1-1][jj] = hz_slave[index][TZ1-1][jj] - 0.7*(ex_slave[index][TZ1-1][jj+1] \
                                - ex_slave[index][TZ1-1][jj] + shade_ey[index][jj] - ey_slave[k%2][TZ1-1][jj]); 
        }
        for(ii=0; ii<TZ1-1; ii++)
        {
            hz_slave[index][ii][TZ2-1] = hz_slave[index][ii][TZ2-1] - 0.7*(shade_ex[index][ii]\
                               - ex_slave[index][ii][TZ2-1] + ey_slave[index][ii+1][TZ2-1] - ey_slave[index][ii][TZ2-1]);
        }
        hz_slave[index][TZ1-1][TZ2-1] = hz_slave[index][TZ1-1][TZ2-1] - 0.7*(shade_ex[index][TZ1-1] \
                                - ex_slave[index][TZ1-1][TZ2-1]+shade_ey[index][TZ2-1] - ey_slave[index][TZ1-1][TZ2-1]);

#ifdef PROFILING      
        comp_end=rtc_();
        dma_put_start=rtc_();
#endif        

        //write back
        //ey_slave[0:TZ1-1,0:TZ2-1]->ey[i:i+TZ1-1,j:j+TZ2-1]
        //dma_put_num=0;
        put_reply[index]=0;
        dma_put_num[index]=0;
        bsize=TZ2*8;
        stride=(NY-TZ2)*8; 
        tsize= bsize*TZ1;
        athread_put(PE_MODE 
            , &hz_slave[index][0][0], &hz[i][j]
            , tsize, (void*)&put_reply[index]
            , stride, bsize);
        dma_put_num[index]++;

        if(k>0)
        {
            while(put_reply[last]!=dma_put_num[last]);
        }
#ifdef PROFILING 
        dma_put_end=rtc_();
        //total_end=rtc_();
        slave_comp_count[my_id]=slave_comp_count[my_id]+(comp_end-comp_start);
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start)+(dma_get_end-dma_get_start);
        //slave_count[my_id]=slave_count[my_id]+(total_end-total_start);
#endif        
    }//end of k
#ifdef PROFILING      
        dma_put_start=rtc_();
#endif
    //step 5: wait for previous write back
    //while_tmp(put_reply[index],dma_put_num[index]);
    while(put_reply[index]!=dma_put_num[index]);
#ifdef PROFILING 
        dma_put_end=rtc_();
        slave_dma_count[my_id]=slave_dma_count[my_id]+(dma_put_end-dma_put_start);
#endif
    athread_syn(ARRAY_SCOPE,-1);  

    }//end of iter
}//end of func
